/**
 * Copyright 2009 University of Washington This code is released under the GPL version 3 - with a copy of the license
 * included
 * 
 * @author Maya Hao Li
 */
package seedpod.webapp.view;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import javax.servlet.http.HttpServletRequest;

import seedpod.exceptions.DataValidationException;
import seedpod.exceptions.SeedpodException;
import seedpod.kb2db.ClsMap;
import seedpod.kb2db.SlotMap;
import seedpod.model.AVPair;
import seedpod.model.NoObjectFoundException;
import seedpod.model.ProtegeValueType;
import seedpod.model.SeedpodDO;
import seedpod.model.Thing;
import seedpod.rdb.PManager;
import seedpod.util.Util;
import seedpod.webapp.LimsSession;
import seedpod.webapp.Seedpod;
import seedpod.webapp.controller.ActionDeleteInstance;
import seedpod.webapp.view.htmlwidget.HtmlLink;
import seedpod.webapp.view.htmlwidget.ImageButton;
import seedpod.webapp.view.htmlwidget.Label;
import seedpod.webapp.view.htmlwidget.Table;
import seedpod.webapp.view.htmlwidget.TableCell;
import seedpod.webapp.view.htmlwidget.TableRow;
import seedpod.webapp.view.seedpodwidgets.LimsWidget;
import seedpod.webapp.view.seedpodwidgets.LimsWidgetFactory;
import seedpod.webapp.view.servlet.ClsViewer;

public class InstanceRenderer {

	private static final long serialVersionUID = -5457345716883413826L;
	public static final String CLSNAME = "clsName";
	public static final String OID = "oid";
	public static final String ACTION = "action";
	public static final String ACTION_VIEW = "view";
	public static final String ACTION_EDIT = "edit";
	public static final String ACTION_CREATE = "create";
	public static final String VALIDATION_ERROR = "valid_errors";
	public static final String PAGE_MSG = "page_msg";

	public static boolean validAction( String action ) {
		if ( action == null ) return false;
		if ( !action.equals( ACTION_EDIT ) && !action.equals( ACTION_VIEW ) && !action.equals( ACTION_CREATE ) )
			return false;
		return true;
	}

	private SeedpodDO _spObject;
	private String _clsName;
	private int _oid = 0;
	private String _action = ACTION_VIEW;
	private HashMap<String, DataValidationException> _validErrors;

	private String _pageMsg = "";

	public InstanceRenderer(HttpServletRequest request) throws SeedpodException {
		LimsSession session = LimsSession.getSession( request );
		initRequiredField( request );
		initInstance( request );
		_pageMsg = (String)session.getAttribute( PAGE_MSG );
	}

	public InstanceRenderer(SeedpodDO obj) throws SeedpodException {
		_clsName = obj.getMetaCls().getName();
		_oid = obj.getOid();
		_spObject = obj;
		if ( !_spObject.isPersistent() && _oid != 0 ) _spObject.retrieveById();
	}

	public void clearPageMsg() {
		_pageMsg = "";
	}

	public boolean doCreate() {
		return _action.equalsIgnoreCase( ACTION_CREATE );
	}

	public boolean doEdit() {
		return _action.equalsIgnoreCase( ACTION_EDIT ) && !_spObject.isDeleted();
	}

	public boolean doView() {
		return _action.equalsIgnoreCase( ACTION_VIEW ) && !_spObject.isDeleted();
	}

	public String getAction() {
		return _action;
	}

	public String getClsName() {
		return _clsName;
	}

	public String getDeleteHtmlLink() {
		String html = "";
		HtmlLink deleteLink = new HtmlLink( ActionDeleteInstance.getUrl( _spObject.getOid() ), "[ delete ]" );
		deleteLink.setCssClass( "menuItemLink" );
		deleteLink.setJsCommand( "onclick=\"Effect.toggle('deleteAlert','appear'); return false;\"" );

		html = deleteLink.render();

		// delete dialogue box: hidden in general
		html += "<span id=\"deleteAlert\" style=\"display:none; position:absolute;\">";
		html += "<div id=\"deleteAlertMsg\" class=\"popup\">";
		html += "Are you sure you want to delete " + getLabel() + "?";
		html += HtmlUtil.NL();
		html += "<a href=\"" + ActionDeleteInstance.getUrl( _oid ) + "\">Yes</a> &nbsp;";
		html += "<a href=\"#\" onClick=\"Effect.toggle('deleteAlert', 'appear'); return false;\">No</a>";
		html += "</div></span>";
		return html;
	}

	public String getEditHtmlLink() {
		HtmlLink editLink = new HtmlLink( "#", "[ edit ]" );
		editLink.setCssClass( "menuItemLink" );
		editLink.setJsCommand( "onclick=\"loadEditForm('" + _clsName + "'," + _oid + "); return false;\"" );
		return editLink.render();
	}

	private String getEditorErrHtml( DataValidationException err, String attName ) {
		if ( err != null ) {
			String id = "error_" + attName;
			String errHtml = "<span class=\"editorErr\" id=\"" + id + "\">";
			errHtml += err.getMessage();
			errHtml += "</span>";
			return errHtml;
		} else return "";
	}

	public String getLabel() {
		Label label = new Label( _spObject.getLabel() );
		label.setCssClass( "instanceLabel " );
		return label.render();
	}

	public int getOid() {
		return _oid;
	}

	public String getPageMsg() {
		return _pageMsg;
	}

	public SeedpodDO getSeedpodDO() {
		return _spObject;
	}

	/**
	 * Prints the type (or metaclass) of the instance. When the type name is clicked, a box will popup displaying the
	 * meta information of the metaclass.
	 * 
	 * @return
	 */
	public String getType() {
		StringBuffer type = new StringBuffer();
		String closeImgUrl = Seedpod.getAbsoluteUrl( "/images/close.gif" );
		ImageButton closeBtn = new ImageButton( "closeCmap", closeImgUrl, "#", "close" );
		closeBtn.addJavascript( "onclick=\"Effect.toggle('clsDefinition','appear'); return false;\" " );

		ClsMap cmap = _spObject.getMetaCls();
		HtmlLink cmapLink = new HtmlLink( "#", cmap.getName() );
		cmapLink.setJsCommand( "onclick=\"Effect.toggle('clsDefinition','appear'); return false;\"" );

		Label objTypeLabel = new Label( HtmlUtil.PARAN( "Instance Type: " + cmapLink.render() ) );
		objTypeLabel.setCssClass( "clsType" );
		type.append( "<span >" + objTypeLabel.render() + "</span>" );

		type.append( "<span id=\"clsDefinition\" style=\"display:none; position:absolute;\">" );
		type.append( "<div id=\"clsViewer\" class=\"popup\">" );
		type.append( closeBtn.render() + "<br>" );
		type.append( ClsViewer.renderClsBox( cmap ) );
		type.append( "</div></span>" );
		return type.toString();
	}

	public boolean hasValidationError() {
		return _validErrors != null && _validErrors.size() > 0;
	}

	@SuppressWarnings("unchecked")
	protected void initInstance( HttpServletRequest request ) throws SeedpodException {
		PManager manager;
		LimsSession spSession = LimsSession.getSession( request );
		manager = Seedpod.getPManager();

		// get object from seedpod session, if fails, retrieve from the database
		if ( _oid >= 0 ) {

			// get cls name if not already given
			if ( _clsName == null ) try {
				_clsName = Thing.getClassType( manager, _oid );
			} catch (NoObjectFoundException e) {
				throw new SeedpodException( "I cannot find the object you requested" );
			}

			_spObject = (SeedpodDO)spSession.getAttribute( Util.getSeedpodInternalId( _clsName, _oid ) );

			if ( _spObject == null ) {
				_spObject = manager.createObject( _clsName, _oid );
				spSession.cacheSeedpodObject( _spObject );
			}

		}

		if ( _spObject == null )
			throw new SeedpodException( "I cannot find the object you requested" );
		else spSession.setAttribute( Util.getSeedpodInternalId( _spObject ), _spObject );

		setErrors( (HashMap)spSession.getAttribute( VALIDATION_ERROR ) );
	}

	private void initRequiredField( HttpServletRequest request ) {
		/** required variables * */
		String oidStr = request.getParameter( OID );
		_clsName = request.getParameter( CLSNAME );
		_action = request.getParameter( ACTION );
		_oid = oidStr != null ? Integer.decode( oidStr ) : 0;

		if ( !validAction( _action ) ) _action = ACTION_VIEW;

	}

	public String renderCreateForm() {
		int numRow = 0;
		ClsMap cmap = _spObject.getMetaCls();
		ArrayList<SlotMap> slotMaps = cmap.getSortedSlotMaps();

		// create a table of the attribute values
		Table newTable = new Table( "newInstanceTable" );
		newTable.setAlternativeRowColor( true );
		newTable.setAlternativeRowCssClass( "odd", "even" );
		newTable.setCssClass( "generic" );
		newTable.setOnMouseOverClass( "onMouseOver" );
		newTable.setTableCellSpacing( 1, 10 );

		// set up instance table columns: attribute name and value
		TableRow headerRow = newTable.addRow();
		headerRow.setIsHeader( true );
		headerRow.setCssClass( "header" );
		newTable.addCell( "Attribute" );
		newTable.addCell( "Value" );

		AVPair avPair;
		TableRow attRow;
		String header;
		String defaultVal;
		String dataEditor;

		// iterate through AttributeValue pairs
		for ( SlotMap atSlotMap : slotMaps ) {
			if ( !atSlotMap.isAssociated() ) continue;

			avPair = _spObject.getAVPair( atSlotMap );

			defaultVal = atSlotMap.getDefaultValue();
			if ( defaultVal != null && defaultVal.length() > 0 ) avPair.setValue( atSlotMap.getDefaultValue() );

			LimsWidget formWidget = LimsWidgetFactory.getWidget( avPair.getAttribute().getFormWidget(), avPair );
			attRow = newTable.addRow();

			numRow++;

			// attribute name cell
			header = atSlotMap.getDisplayName(); // atSlotMap.getName();
			TableCell headerCell = attRow.addCell( header );

			if ( !atSlotMap.isNullable() ) {
				headerCell.appendCellContent( "*" );
				headerCell.setCssClass( "headerCell" );
			}

			// value cell
			dataEditor = formWidget.doEdit();
			TableCell dataCell = attRow.addCell( dataEditor );
			dataCell.setCssClass( "dataCell" );
		}
		return numRow == 0 ? null : newTable.render();

	}

	public String renderEditForm() {
		ClsMap cmap = _spObject.getMetaCls();
		Collection<SlotMap> slotmaps = cmap.getSlotMaps();
		Iterator<SlotMap> smapItr = slotmaps.iterator();

		String html = "";
		boolean hasError = hasValidationError();

		// create a table of the attribute values
		Table editTable = new Table( "instanceTable" );
		editTable.setAlternativeRowColor( true );
		editTable.setAlternativeRowCssClass( "odd", "even" );
		editTable.setCssClass( "generic" );
		editTable.setOnMouseOverClass( "onMouseOver" );
		editTable.setTableCellSpacing( 1, 10 );

		// set up instance table columns: attribute name and value
		TableRow headerRow = editTable.addRow();
		headerRow.setIsHeader( true );
		headerRow.setCssClass( "header" );
		editTable.addCell( "Attribute" );
		editTable.addCell( "Value" );

		// iterate through AttributeValue pairs
		while (smapItr.hasNext()) {
			SlotMap atSlotMap = smapItr.next();

			/** delay editing of relationships until later **/
			if ( atSlotMap.getProtegeValueType().equals( ProtegeValueType.Instance ) && atSlotMap.isMultiple() )
				continue;

			TableRow attRow = editTable.addRow();

			// attribute name cell
			String header = atSlotMap.getDisplayName(); // getName();
			TableCell headerCell = attRow.addCell( header );
			if ( !atSlotMap.isNullable() ) {
				headerCell.appendCellContent( "*" );
				headerCell.setCssClass( "headerCell" );
			}

			// attribute value cell
			AVPair avPair = _spObject.getAVPair( atSlotMap );
			LimsWidget formWidget = LimsWidgetFactory.getWidget( avPair.getAttribute().getFormWidget(), avPair );
			TableCell dataCell = attRow.addCell( formWidget.doEdit() );
			dataCell.setCssClass( "dataCell" );

			// print error
			if ( hasError ) {
				String errorHtml = getEditorErrHtml( _validErrors.get( avPair.genRefId() ), atSlotMap.getName() );
				dataCell.appendCellContent( errorHtml );
			}
		}

		html += editTable.render();
		return html;
	}

	public String renderInstance() {
		ClsMap cmap = _spObject.getMetaCls();

		// create a table of the attribute values
		Table dataTable = new Table( "instanceTable" );
		dataTable.setAlternativeRowColor( true );
		dataTable.setAlternativeRowCssClass( "odd", "even" );

		// depending on whether the instance is still active, make them look different
		if ( _spObject.isDeleted() ) {
			dataTable.setCssClass( "inactive" );
			dataTable.setOnMouseOverClass( "inactaive" );
		} else dataTable.setCssClass( "generic" );
		// dataTable.setOnMouseOverClass( "onMouseOver" );

		dataTable.setTableCellSpacing( 1, 5 );

		// set up instance table columns: attribute name and value
		TableRow headerRow = dataTable.addRow();
		headerRow.setIsHeader( true );
		headerRow.setCssClass( "header" );
		dataTable.addCell( "Attribute" );
		dataTable.addCell( "Value" );

		// iterate through AttributeValue pairs
		Collection<SlotMap> slotmaps = cmap.getSortedSlotMaps();
		Iterator<SlotMap> smapItr = slotmaps.iterator();

		while (smapItr.hasNext()) {
			SlotMap atSlotMap = smapItr.next();

			AVPair val = _spObject.getAVPair( atSlotMap );
			LimsWidget viewWidget = LimsWidgetFactory.getWidget( val.getAttribute().getViewWidget(), val );
			TableRow attRow = dataTable.addRow();

			// attribute name cell
			TableCell headerCell = attRow.addCell( atSlotMap.getDisplayName() );
			if ( !atSlotMap.isNullable() ) {
				headerCell.appendCellContent( "*" );
				headerCell.setCssClass( "requiredHeading" );
			} else headerCell.setCssClass( "attributeNameCell" );

			// value cell
			String cellContent = viewWidget.doView();
			String dataCellId = "cell_" + val.genRefId();

			TableCell dataCell = attRow.addCell( cellContent );
			dataCell.setCssClass( "dataCell" );
			dataCell.setId( dataCellId );

			if ( viewWidget.allowInPlaceEdit() )
				dataCell.setJScript( "\"onMouseOver=\"this.className='inPlaceEdit'\" onMouseOut=\"this.className=''\" onClick=\"this.innerHTML=loadInPlaceEdit('" + dataCellId + "', '" + cmap.getName() + "', '" + atSlotMap.getName() + "', " + _oid + ");return false;\" " );
		}
		return dataTable.render();
	}

	public void setErrors( HashMap<String, DataValidationException> errors ) {
		_validErrors = errors == null ? new HashMap<String, DataValidationException>() : errors;
	}

	public void setPageMsg( String msg ) {
		_pageMsg = msg;
	}

}
